#include "Zombie.h"
#include "ZombieManager.h"
#include "ZombieSwarm.h"

Ogre::NameGenerator Zombie::_bloodNameGenerator("zblood");

Zombie::Zombie(btDiscreteDynamicsWorld* pWorld,
               btCylinderShape* pZombieCollisionShape,
               Ogre::SceneManager* pSceneManager,
               OpenSteer::Vec3 position,
               Human* pHuman,
               ZombieSwarm* pSwarm,
               bool boss) :
  Character::Character(pWorld, pZombieCollisionShape, pSceneManager,
                       position, ZOMBIEMESH),
  _pHuman(pHuman), _pSwarm(pSwarm) {

  // Tipo de zombi
  int zombieType = (boss ? 3 : rand() % 3);

  switch (zombieType) {

  case 0:
    _pEntity->setMaterialName(ZOMBIE1); break;
  case 1:
    _pEntity->setMaterialName(ZOMBIE2); break;
  case 2:
    _pEntity->setMaterialName(ZOMBIE3); break;
  case 3:
    _pEntity->setMaterialName(ZOMBIE4); break;
  }

  // Gestor de zombis
  _pZombieManager = ZombieManager::getSingletonPtr();

  // Query mask
  _pEntity->setQueryFlags(QUERY_MASK_ZOMBIE);

  // Entity user
  _pEntity->setUserAny(Ogre::Any(this));

  // SceneNode
  if (boss) _pNode->setScale(3, 3, 3);

  // AnimationState
  Ogre::AnimationState* zombieAnimation =
    _pEntity->getAnimationState("Walk");
  zombieAnimation->setEnabled(true);
  zombieAnimation->setLoop(true);
  zombieAnimation->setTimePosition(rand() % 10);

  // Bullet vehicle
  setMaxForce(10);
  setMaxSpeed(2);

  // Character
  setLife((boss ? 100 : 1 + rand() % 5));
}

void
Zombie::update(const float currentTime, const float elapsedTime) {

  // Orientar el nodo del zombi respecto de su movimiento.
  // Esto no debería hacerse aquí, pero por ahora es la única solución
  // que se me ha ocurrido.

  // Versión 2: En lugar de orientar el nodo, se orienta el cuerpo
  // rígido, y ya se encarga MyMotionState de orientar el nodo

  // Obtener vector dirección en Ogre
  Ogre::Vector3 ogreForward(forward().x, forward().y, -forward().z);

  // Obtener cuaternio de rotación
  Ogre::Quaternion ogreRotation =
    ogreForward.getRotationTo(Ogre::Vector3::NEGATIVE_UNIT_Z);

  // Convertir a cuaternio bullet
  btQuaternion bulletRotation(ogreRotation.x,
                              ogreRotation.y,
                              ogreRotation.z,
                              ogreRotation.w);

  // Obtener matriz de transformación con la nueva rotación
  btTransform bulletTransform(bulletRotation,
                              _pRigidBody->getCenterOfMassPosition());

  // Aplicar nueva transformación
  _pRigidBody->activate();
  _pRigidBody->setCenterOfMassTransform(bulletTransform);

  float distanceToHuman =
    OpenSteer::Vec3::distance(position(), _pHuman->position());

  if (distanceToHuman <
      _pCollisionShape->getHalfExtentsWithoutMargin().y() * 2)
    _pHuman->doDamage(1);

  // Animar el zombi
  _pEntity->getAnimationState("Walk")->addTime(elapsedTime);

  // Aplicar fuerzas de comportamientos
  applySteeringForce(steerForZombie(elapsedTime));
}

/*
  Esta función determina el comportamiento de los zombies. En esta versión
  es el siguiente:

  1: Avanzar.
  2: Vagar.
  3: Perseguir al humano
  4: "Llamada zombi" si el humano se acerca a la horda
  5: Ir al centro de la horda si se superó la distancia máxima.

 */
OpenSteer::Vec3
Zombie::steerForZombie(const float elapsedTime) {

  OpenSteer::Vec3 zombieForce(0, 0, 0);

  // Avanzar -----------------------------------------------------------

  // Si la velocidad es poca, avanzar (XXX ¿Añadir _minSpeed?)
  if (speed() < 1) zombieForce += forward();

  // Vagar -------------------------------------------------------------

  zombieForce += steerForWander(elapsedTime);

  // Perseguir al humano -----------------------------------------------

  float distanceToHuman =
    OpenSteer::Vec3::distance(position(), _pHuman->position());

  if (distanceToHuman < MINDISTANCETOHUMAN) {

    zombieForce += steerForSeek(_pHuman->position());
    return zombieForce.setYtoZero();
  }

  // Llamada zombi -----------------------------------------------------

  // Si el zombi no pertenece a ninguna orda, se ignora el comportamiento
  if (_pSwarm == NULL) return zombieForce.setYtoZero();

  float swarmDistanceToHuman =
    OpenSteer::Vec3::distance(_pSwarm->swarmCenter(),
                              _pHuman->position());

  if (swarmDistanceToHuman <
      _pSwarm->maxDistance() + MINDISTANCETOHUMAN) {

    zombieForce += steerForSeek(_pHuman->position());
    return zombieForce.setYtoZero();
  }

  // Ir al centro de la horda ------------------------------------------

  float distanceToSwarm =
    OpenSteer::Vec3::distance(position(), _pSwarm->swarmCenter());

  if (distanceToSwarm > _pSwarm->maxDistance())
    zombieForce += steerForSeek(_pSwarm->swarmCenter());

  // Devolver fuerza eliminando la componente Y ------------------------

  return zombieForce.setYtoZero();
}

bool Zombie::doDamage(int damage)
{
  bool r = false;
  _life = _life - damage;

  Ogre::ParticleSystem* ps =
    _pSceneManager->createParticleSystem
    (_bloodNameGenerator.generate(),
     "zblood");
  _pNode->attachObject(ps);

  if (_life <= 0)
  {
    doDead();
    r = true;
  }

  return r;
}

void
Zombie::doDead() {

  if (_pSwarm != NULL)
    _pSwarm->removeZombie(this);

  _pZombieManager->deleteZombie(this);
}
